package com.autumn.platform.core.reflect.impl;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import com.autumn.platform.core.logger.Logs;
import com.autumn.platform.core.reflect.ConvertService;
import com.autumn.platform.core.reflect.FieldSetting;

public abstract class AbstractConvertService<S> implements ConvertService<S> {

    @SuppressWarnings({"rawtypes"})
    private static final FieldSetting[] EMPTY = new FieldSetting[0];

    private final Map<String, FieldSetting<S>[]> cache = new ConcurrentHashMap<>();

    @Override
    public <T> T convert(S src, Class<T> cls, Object... args) {
        T bean = this.newInstance(cls);
        for (Class<?> c = cls; !c.equals(Object.class); c = c.getSuperclass()) {
            FieldSetting<S>[] settings = getFieldSetting(c);
            if (null != settings && settings.length > 0) {
                for (FieldSetting<S> s : settings) {
                    s.set(bean, src, args);
                }
            }
        }
        return bean;
    }

    abstract protected FieldSetting<S> resolveFieldSetting(Field field);

    protected <T> T newInstance(Class<T> cls) {
        T bean = null;
        try {
            bean = cls.newInstance();
        } catch (Exception e) {
            try {
                Constructor<T> constructor = cls.getConstructor();
                if (!constructor.isAccessible()) {
                    constructor.setAccessible(true);
                }
                bean = constructor.newInstance();
            } catch (Exception ce) {
                Logs.error("create instance error [" + cls + "]", e);
                throw new RuntimeException(e);
            }
        }
        return bean;
    }

    @SuppressWarnings("unchecked")
    private FieldSetting<S>[] getFieldSetting(Class<?> cls) {
        String key = cls.getName();
        FieldSetting<S>[] fieldSettings = cache.get(key);
        if (null == fieldSettings) {
            synchronized (cache) {
                fieldSettings = cache.get(key);
                if (null == fieldSettings) {
                    List<FieldSetting<S>> settings = new ArrayList<>();
                    Field[] fields = cls.getDeclaredFields();
                    if (null != fields) {
                        for (Field field : fields) {
                            int mod = field.getModifiers();
                            if (Modifier.isStatic(mod) || Modifier.isFinal(mod) || field.isSynthetic()) {
                                continue;
                            }
                            if (!field.isAccessible()) {
                                field.setAccessible(true);
                            }
                            FieldSetting<S> fieldSetting = this.resolveFieldSetting(field);
                            if (null != fieldSetting) {
                                settings.add(fieldSetting);
                            }
                        }
                    }
                    if (settings.isEmpty()) {
                        fieldSettings = EMPTY;
                    } else {
                        fieldSettings = settings.toArray(new FieldSetting[0]);
                    }
                    cache.put(key, fieldSettings);
                }
            }
        }
        return fieldSettings;
    }
}
